package org.weed.remote.http;

import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.spi.SelectorProvider;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class HttpServer implements Runnable {
	public static final int NUMBER_OF_THREADS = 5;
	protected InetAddress hostAddress;
	protected int port;
	protected InetSocketAddress socketAddress;
	
	protected ServerSocketChannel serverChannel;
	protected Selector selector;
	
	private ExecutorService executor;
	
	private boolean isAlive;
	private Logger logger;
	private int totalKeyCount, acceptableKeyCount, readableKeyCount, writableKeyCount;
	
	public HttpServer(InetAddress hostAddress, int port) {
		this.hostAddress = hostAddress;
		this.port = port;
		isAlive = false;
		logger = new Logger(getClass().getName());
		executor = Executors.newFixedThreadPool(NUMBER_OF_THREADS);
	}
	
	protected boolean init() {
		try {
			Selector socketSelector;
			socketSelector = SelectorProvider.provider().openSelector();
			
			this.serverChannel = ServerSocketChannel.open();
			serverChannel.configureBlocking(false);
			
			this.socketAddress = new InetSocketAddress(hostAddress, port);
			serverChannel.socket().bind(socketAddress);
			
			serverChannel.register(socketSelector, SelectionKey.OP_ACCEPT);			
			this.selector = socketSelector;
			return true;
		} catch (Exception e) {
			logger.error("Unable to start server at address[" + hostAddress + "] and port[" + port + "].");
			return false;
		}
	}
	
	public void run() {
		isAlive = init();
		logger.info("Server started at " + socketAddress + ".");
		while (isAlive) {
			initStats();
			try {
				selector.select();
				Set<SelectionKey> keySet = selector.selectedKeys();
				totalKeyCount = keySet.size();
				Iterator<SelectionKey> keys = keySet.iterator();
				
				while (keys.hasNext()) {
					SelectionKey key = (SelectionKey) keys.next();
					keys.remove();
					if (key.isValid()) {
						if (key.isAcceptable()) {
							acceptableKeyCount++;
							doAccept(key); // server key
						} else if (key.isReadable() || key.isWritable()) {
							HttpConnection connection = (HttpConnection) key.attachment();
							if (key.isReadable()) {
								readableKeyCount++;
								connection.recvHttpRequest(executor);
							} else {
								writableKeyCount++;
								connection.sendHttpResponse(executor);
							}
							key.interestOps(HttpConstants.NONE);
						}
					}
				}
				
				printStats();
			} catch (Exception e) {
				
			}
		}
	}
	
	public void doAccept(SelectionKey key) {
		try {
			HttpConnection connection = new HttpConnection(key);
		} catch (IOException e) {
			logger.info("Broken pipe.");
			e.printStackTrace();
		}
	}
	
	private void initStats() {
		totalKeyCount = 0;
		acceptableKeyCount = 0;
		readableKeyCount = 0;
		writableKeyCount = 0;
	}
	private void printStats() {
		if (totalKeyCount > 0) {
			System.out.println("Total: "+totalKeyCount+", Accepted: " + acceptableKeyCount +
					", read: " + readableKeyCount + ", written: " + writableKeyCount);
		}
	}
	
	public static void main(String[] args) {
		HttpServer server = new HttpServer(null, 80);
		server.run();
	}
}
